#include "Scheduler.h"
#include "ITask.h"

#include <stdio.h>
#include <vector>

#define RENDER_TASK_ID 1

Scheduler::Scheduler()
{
	m_nextId = RENDER_TASK_ID + 1;
}

Scheduler* Scheduler::Instance()
{
	static Scheduler instance;

	return &instance;
}


Scheduler::~Scheduler()
{
	// release our allocated memory
	std::vector<TaskInfo*>::iterator itr = m_taskQueue.Begin();
	while (itr != m_taskQueue.End())
	{
		delete *itr;
		itr++;
	}

	itr = m_frameQueue.Begin();
	while (itr != m_frameQueue.End())
	{
		delete *itr;
		itr++;
	}
}

void Scheduler::InsertFrameTask(TaskInfo* pTaskInfo)
{
	m_frameQueue.insert(pTaskInfo);
}

void Scheduler::InsertTimeTask(TaskInfo* pTaskInfo)
{
	m_taskQueue.insert(pTaskInfo);
}

void Scheduler::GetNextFrameTask(TaskInfo** ppTaskInfo)
{
	// return the next frame task. If no frame task is scheduled to run this frame,
	// return null. note that this method REMOVES the task from the queue, so the
	// caller is responsible for either adding it back in (rescheduling) or
	// deleting it.
	TaskInfo* pNextTask = m_frameQueue.peek();
	if (pNextTask && pNextTask->time.next <= m_clock.GetFrame())
	{
		*ppTaskInfo = m_frameQueue.pop();
		return;
	}
	*ppTaskInfo = NULL;
}

void Scheduler::GetNextTimeTask(TaskInfo **ppTaskInfo)
{
	// return the next time task. If no time task is scheduled to run this frame,
	// return null. note that this method REMOVES the task from the list, so the
	// caller is responsible for either adding it back in (rescheduling) or
	// deleting it.
	TaskInfo *pNextTask = m_taskQueue.peek();
	if (pNextTask && pNextTask->time.next <= m_clock.GetFrameEnd())
	{
		*ppTaskInfo = m_taskQueue.pop();
		return;
	}
	*ppTaskInfo = NULL;
}

int Scheduler::Schedule(TaskType type, int start, int period, int duration, ITask* pTask, void* pUser, int* pID)
{

	// this schedules a task of the appropriate type (FRAME, or TIME). Time tasks and
	// frame tasks are kept in separate queues for ease of handling.
	// time and frame tasks have a start time, a duration, and a period.
	// the duration is relative to the start time, except for duration <=0 which is a special case: <0 execute once, ==0 execute infinite.
	// since the scheduler doesn't care about the duration itself, it converts it into an end time
	// and stores that instead. 
	// pUser is a user data pointer.
	// a unique task id is generated and returned in pID. if you don't care about an ID,
	// pass in NULL instead.


	TaskInfo* pTaskInfo = new TaskInfo;

	if (pTaskInfo)
	{
		pTaskInfo->pTask = pTask;
		pTaskInfo->status = 0;
		pTaskInfo->id = m_nextId++;
		if (pID)
			*pID = pTaskInfo->id;
		pTaskInfo->pUser = pUser;
		pTaskInfo->time.start = start;
		pTaskInfo->time.period = period;
		if (duration == 0)
			pTaskInfo->time.duration = 0; // infinite
		else
			pTaskInfo->time.duration = start + duration - 1; // compute end time
		pTaskInfo->time.next = start;

		ShowDebug("Scheduling %s task %u from %3u to %3u, every %2u %s\n",
			type==TASK_TIME?"time ":"frame",
			(int)pTaskInfo->id,
			pTaskInfo->time.start,
			pTaskInfo->time.duration,
			pTaskInfo->time.period,
			type==TASK_TIME?"ms":"frames");

		if (type == TASK_TIME)
			InsertTimeTask(pTaskInfo);
		else if (type == TASK_FRAME)
			InsertFrameTask(pTaskInfo);
		return 1;
	}
	else return 0;

}

void Scheduler::Terminate(int id)
{
	//notice: this method don't support multithread.
	int found = false;

	std::vector<TaskInfo*>::iterator itr = m_taskQueue.Begin();
	while (itr != m_taskQueue.End() && *itr != NULL)
	{
		if ((*itr)->id == id)
		{
			delete *itr;
			m_taskQueue.Erase(itr);
			break;
		}

		itr++;
	}

	itr = m_frameQueue.Begin();
	while (itr != m_frameQueue.End() && *itr != NULL)
	{
		if ((*itr)->id == id)
		{
			delete *itr;
			m_frameQueue.Erase(itr);
			break;
		}
		itr++;
	}
}

int Scheduler::DoTimer(int delta)
{
#ifdef SHOW_SCHEDULE_INFO
	ShowDebug("EXEC BEGIN\n");
#endif
	//
	// Run one frame. This takes the time stamp marking the end of the frame
	// and then processes events for that frame retroactively. This method has
	// the advantage of flexibility, especially if the frame rate fluctuates.
	// However it is always a little behind, because it can't compute the
	// frame length until the end of the frame is reached. With a fixed known
	// frame rate you could optimize things a bit and make the start/end times
	// correspond exactly with real time.
	//
	m_clock.BeginFrame();
	int started = m_clock.GetSystem();

	//
	// Execute any time-based tasks
	//
	// (1) Pop the next task off the queue. Since the queue is always
	//     sorted, the first item in the queue is always the next task.
	// (2) Execute it and update times
	// (3) If it's expired, delete it
	//     Otherwise, insert it into the list in its new position
	//
	TaskInfo * pTaskInfo = NULL;
	GetNextTimeTask(&pTaskInfo);
	while (pTaskInfo)
	{
		m_clock.AdvanceTo(pTaskInfo->time.next);

		pTaskInfo->pTask->Execute(pTaskInfo->id, delta, pTaskInfo->pUser);
		pTaskInfo->time.last = pTaskInfo->time.next;
		pTaskInfo->time.next += pTaskInfo->time.period;

		if (pTaskInfo->time.duration < 0)
		{
			// task execute only once, delete it
			ShowDebug("Sched: Expired %d\n",(int)pTaskInfo->id);
			delete pTaskInfo;
		}

		else if (pTaskInfo->time.duration == 0 ||
			pTaskInfo->time.duration >= pTaskInfo->time.next)
		{
			// re-insert into list with updated time
			InsertTimeTask(pTaskInfo);
		}
		else
		{
			// task is expired, delete it
			ShowDebug("Sched: Expired %d\n",(int)pTaskInfo->id);
			delete pTaskInfo;
		}
		GetNextTimeTask(&pTaskInfo);
	}
	//
	// Advance simulation clock to end of frame
	//
	m_clock.AdvanceToEnd();

	//
	// Now execute all frame tasks in round-robin fashion.
	// Frame tasks always execute at the end of the frame just
	// before rendering. A priority scheme could be used to
	// control sequence. It would be more efficient to keep the
	// queue sorted, the same as with time tasks (exe
	
	GetNextFrameTask(&pTaskInfo);
	while (pTaskInfo)
	{
		pTaskInfo->pTask->Execute(pTaskInfo->id, delta , pTaskInfo->pUser);
		pTaskInfo->time.last = pTaskInfo->time.next;
		pTaskInfo->time.next += pTaskInfo->time.period;

		if (pTaskInfo->time.duration == 0 ||
			pTaskInfo->time.duration >= pTaskInfo->time.next)
		{
			// re-insert into list with updated time
			InsertFrameTask(pTaskInfo);
		}
		else
		{
			// task is expired, delete it
			ShowDebug("Sched: Expired %d\n",(int)pTaskInfo->id);
			delete pTaskInfo;
		}
		GetNextFrameTask(&pTaskInfo);
	}

	// here is where we could do idle processing or load balancing
	//
	int elapsed = m_clock.GetSystem() - started;
	int frameLength = m_clock.GetFrameEnd() - m_clock.GetFrameStart();
#ifdef SHOW_SCHEDULE_INFO
	ShowDebug("Busy %u ms, idle %u ms\n", (int)elapsed, (int)(frameLength - elapsed));

	ShowDebug("EXEC END\n");
#endif
	return 0;
}


